package model;

import model.i.IRules;
import model.playingfield.PlayingField;

import org.bushe.swing.event.EventBus;

import constants.Constants;
import constants.Events;

/**
 * This is the Rules class. It specifies how the Model should respond
 * to events. It is the most important part of the game logic.
 * <br><br>
 * The game follows a series of events - a player is selected, the die
 * is rolled, a piece is selected (and moved). For each event, there is
 * a large number of consequences that can follow. The role of the Rules class
 * is to provide a modular interface for defining exactly what those consequences are.
 * <br><br>
 * Whenever Model receives an event, it will call a function in Rules that
 * specifies what should happen. This allows for great flexibility, since the Rules
 * class can easily be exchanged in order to facilitate new rules, as long as the interface
 * IRules is fulfilled.
 */

public class Rules implements IRules {

    /*
     * The following are state variables for Rules. They are critical, since their being
     * or not being toggled determines how the game should flow. At any point during execution,
     * whenever they have possibilities to, players are able to change these rules.
     */

    boolean pushAllowed;
    boolean enterGoalEasyAllowed;

    Model model;

    public Rules(Model model) {
        this.model = model;
        this.pushAllowed = false;
        this.enterGoalEasyAllowed = false;
    }

    /**
     * This method is called to clean up the current players turn -
     * in other words, save all statistical details, reset all state
     * variables, and pass the turn counter on to the next player.
     */
    @Override
    public void cleanUp() {
        // TODO Auto-generated method stub

    }

    /**
     * Defines how the game should deal with a dieroll.
     * In this instance of the rules, we will simply check if the player
     * has any pieces to move out of his or her starting area. If not, we
     * should pass on the turn to the next player here and now.
     */
    @Override
    public void dieRolled(Integer dieResult) {

        int player = model.getState().getCurrentPlayer();

        /*
         * Are we at the correct location in the order of events? Check first
         * that we have not already done any future tasks this turn, such as
         * moving, or rolling the die (which we should be doing now, of course)
         */

        if(model.getState().isDieRolled() || model.getState().hasMoved()) {

            EventBus.publish(Events.ERROR.toString(), "You have already rolled the die this turn!");
            return;
        }
        model.getState().setCurrentDieValue(dieResult);
        model.getState().setDieRolled(true);


        /*
         * We are in the correct order of events, but is the player even able to move
         * any of his or her pieces with the current die value?
         */
        if(!isMovePossible(dieResult, model.getState().getPlayerLocations()[player])){
            EventBus.publish(Events.ERROR.toString(), "Sorry, you are not able to make any legal moves this turn!");
            model.newTurn();
            return;
        }
    }

    /**
     * Calculates if any move at all is possible.
     * @param dieResult The die's current value.
     * @param locations The locations of a player's pieces.
     * @return true if and only if at least one piece can be moved
     */
    private boolean isMovePossible(int dieResult, Integer[] locations){
        PlayingField playingfield = model.getPlayingField();
        boolean[] results = new boolean[locations.length];

        boolean departValue = false;

        if(dieResult == 1 || dieResult == 6) {
            departValue = true;
        }
//51,50,66,67
        CHECK:
            for(int i = 0; i < locations.length; i++){
                System.out.println("Check: "+i);
                boolean onField = false,
                onHomeRow   = false;

                if(locations[i] <= 55) {   // a piece outside the starting area!
                    onField = true;
                    if(locations[i] >= 40) //the piece is on a homerow
                        onHomeRow = true;
                }
                //If this piece is not in play and the die is not 1 or 6
                if(!onField && !departValue){
                    results[i] = false;
                }else { //This piece is either in play or able to be put into play
                    int goingTo = playingfield.findDestinationSpot(locations[i], dieResult); //Where will it go?
                    if(goingTo == -1){
                        results[i] = false;
                        break CHECK;
                    }
                    if(goingTo == -2){
                        results[i] = true;
                        break CHECK;
                    }
                    for(int location:locations){
                        if( goingTo == location){
                            results[i] = false;
                            break CHECK; //calculate next piece
                        }
                    }
                    //The piece is in play, or able to be put into play. It will not collide with
                    //any piece of the same colour. Is it in its homerow?
                    if(onHomeRow){
                        if(locations[i]%4+dieResult < 4){
                            results[i] = true;
                        }else
                            results[i] = false;
                    }else //It's not in its homerow. Then it can be moved!
                        results[i] = true;
                }
            }

        //If any piece can be moved, return true, else return false.
        for(boolean b: results){
            if(b)
                return true;
        }

        return false;
    }

    /**
     * Alright, so the player made it past the dieroll part...time to shuffle some
     * pieces around.
     *
     * This is the single most heavy part of this setup of the rules, and involves
     * both moving the actual pieces, as well as handling any interaction with other
     * players at this stage.
     *
     * Essentially, this stage will boil down to the following parts:
     *    1. try moving the piece at the given location. This involves checking:
     *      a. Is there even a piece there?
     *      b. If there is, does that piece belong to the given player?
     *      c. If it does (ok, ok, you MAY move it...), where is the piece?
     *         - If it is on the starting field, it should be moved to that players entry spot.
     * 		   - If it is on the main circuit, it should be moved forward according to the die value.
     *           We should take special care in the course of this to make sure we enter the correct
     *           road to the nest for that player, if that is how far we have moved.
     * 		   - If it is on the road to the nest for that player, we should make sure that the value
     * 	         of the die does not send the player "further away" than the nest. For example, if the
     * 	         piece is 3 moves from the nest, then the piece should not be moved. If it is moved
     * 	         into the nest, it should stay there.
     *      d. If the piece lands on another players piece, that other players piece should be moved back
     *         to that players starting area.
     */
    @Override
    public void movePiece(int from, int moves) {

        /*
         * Verify that the player has rolled the die before attempting to move a piece.
         */
        if(!model.getState().isDieRolled()){
            EventBus.publish(Events.ERROR.toString(), "You have to roll the die before moving stuffz");
            return;
        }

        /*
         * Are we at the correct location in the order of events, again? Check first of all
         * that we have not already done a move this turn. Else, fire an ERROR.
         */
        if(model.getState().hasMoved()) {
            EventBus.publish(Events.ERROR.toString(), "You have already done a move this turn!");
            return;
        }

        /*
         * Is there a piece in the given location? if not, we should fire an ERROR event
         * and notify the user that that was a bad, bad, BAD move(TM).
         */
        if(!(model.getPlayingField().isSpotOccupied(from))) {
            EventBus.publish(Events.ERROR.toString(), "There is no piece in that location!");
            return;
        }

        /*
         * Create some temporary variables
         */
        PlayingField playingfield = model.getPlayingField();
        //Integer[][] pieceLocations = playingfield.getPieceLocations();
        Integer currentPlayer = model.getState().getCurrentPlayer();

        /*
         * If there is a spot in that location, does it belong to the proper player? If not,
         * again fire an ERROR and break.
         */
        if(!playingfield.whoOccupiesSpot(from).equals(currentPlayer)) {
            EventBus.publish(Events.ERROR.toString(), "You are not allowed to touch that piece! Shoo!");
            return;
        }

        /*
         * Case 1: The piece is located in a players starting area. In this case, just move it to that
         * players first spot on the circuit.
         */
        if( 56 <= from && from <= 71 ) {

            /*
             * Fire an error if the player doesn't have a legitimate way of moving his or her
             * piece from the nest. This means:
             *   - if the dievalue is not 1 or 6, an error should be raised
             *   - if there already is a piece belonging to that player in the
             *     entry spot, an error should also be raised.
             */
            if(1 != moves && moves != 6 ) {
                EventBus.publish(Events.ERROR.toString(), "You cannot move a piece from the starting location, unless you roll a 1 or a 6");
                return;
            }

            controlledMove(from, moves);
            hasWonGame();
            model.getAudio().playSound(Constants.Sounds.moveOut);
            model.newTurn();
            return;
        }

        /*
         * Case 2: the Piece is located on the main circuit. If this is the case, then we will just
         * go ahead and move them forward for the set number of steps.
         */
        if( 0 <= from && from <= 39){
            controlledMove(from, moves);
            model.getAudio().playSound(Constants.Sounds.moveStep);
            hasWonGame();
            model.newTurn();
            return;
        }

        /*
         *  Case 3: the Piece is located on one of the runways leading up to the nest.
         */
        if( 40 <= from && from <= 71 ) {
            if(from%4+moves < 4){
                controlledMove(from, moves);
                model.getAudio().playSound(Constants.Sounds.moveStep);
            }else
                System.out.println("Rules.movePiece():Ignoring runway move");
            model.newTurn();
            return;
        }
    }

    @Override
    public void playerSelected(Player player) {
        // TODO Auto-generated method stub
    }

    @Override
    public void setPushAllowed(boolean b) {
        this.pushAllowed = b;
    }

    @Override
    public boolean isPushAllowedOn() {
        return this.pushAllowed;
    }

    @Override
    public void setEnterGoalEasy(boolean b) {
        this.enterGoalEasyAllowed = b;
    }

    @Override
    public boolean isEnterGoalEasyOn() {
        return this.enterGoalEasyAllowed;
    }

    /**
     * This method handles the movement of pieces on the field.
     *
     * If a player lands on a square where his/her own piece already is, nothing
     * should happen apart from an error being displayed.
     *
     * If a player lands on another players piece, and the push option is enabled,
     * then that piece should be returned to its starting area, and the new players
     * piece should replace it on the circuit.
     * @param location
     */
    private void controlledMove(int from, int moves) {
        System.out.println("Ctrl Move");

        int currentPlayer = model.getState().getCurrentPlayer();
        PlayingField playingfield = model.getPlayingField();
        int destinationSpot = playingfield.findDestinationSpot(from, moves);
        System.out.println(""+destinationSpot);
        if(destinationSpot == -2){
            destinationSpot = currentPlayer*10 + 1;
        }
        /*
         * If the destination spot is not occupied, just move there and go along.
         */
        if(!playingfield.isSpotOccupied(destinationSpot)) {
            playingfield.movePiece(from, moves);

            //Update the score for the player (score == number of moves)
            int old = model.getState().getPlayer(currentPlayer).getScore();
            model.getState().getPlayer(currentPlayer).setScore(old+moves);

            return;
        }

        /*
         * If the destination spot is in the nest (or beyond it, depending on the rules,
         * remove the piece from play.
         */
        if(destinationSpot == -1) {
            playingfield.removePiece(from);
            return;
        }

        /*
         * If the destination spot is already occupied by a Piece belonging to the
         * current Player, invalidate the move and move on.
         */
        if( playingfield.whoOccupiesSpot(destinationSpot) == currentPlayer ) {
            EventBus.publish(Events.ERROR.toString(), "That spot is already occupied by your own piece!");
            return;
        }

        /*
         * If the destination Spot is occupied by a Piece belonging to another Player, fight them
         * to the death!
         */
        if( playingfield.whoOccupiesSpot(destinationSpot) != currentPlayer ) {
            Battle b = new Battle();
            model.getState().setBattle(b);
            model.getAudio().playSound(Constants.Sounds.battle);
            lastFrom = from;
            lastMoves = moves;
            lastDestinationSpot = destinationSpot;
            b.askPlayer();

            return;
        }

        if(destinationSpot >=40)
            model.getAudio().playSound(Constants.Sounds.moveHome);
    }

    int lastFrom, lastMoves, lastDestinationSpot;

    public void resolveBattle(boolean win){
        PlayingField playingfield = model.getState().getPlayingField();
        if(win){
            model.getAudio().playSound(Constants.Sounds.battleWin);
            playingfield.moveToStart(lastDestinationSpot);
            playingfield.movePiece(lastFrom, lastMoves);
        }
        else{
            model.getAudio().playSound(Constants.Sounds.battleLose);
            playingfield.moveToStart(lastFrom);
        }
    }

    /**
     * This method checks whether a given player has won the game. If this is the case,
     * then, depending on the rules, that player should be removed from the game, and
     * the game can either continue, exit, or whatever is defined by the current set
     * of rules.
     */
    public void hasWonGame() {

        boolean won = false;

        int playerID = model.getState().getCurrentPlayer();
        Player player = model.getState().getPlayer(playerID);

        for(int piece : model.getState().getPlayerLocations()[playerID]) {

            if(playerID == 0){
                if(piece == 41 || piece == 42 || piece == 43 || piece == 40 ){
                    won = true;
                }
                else{
                    return;
                }
            }
            if(playerID == 1){
                if(piece == 44 || piece == 45 || piece == 46 || piece == 47 ){
                    won = true;
                }
                else{
                    return;
                }
            }
            if(playerID == 2){
                if(piece == 48 || piece == 49 || piece == 50 || piece == 51 ){
                    won = true;
                }
                else{
                    return;
                }
            }
            if(playerID == 3){
                if(piece == 52 || piece == 53 || piece == 54 || piece == 55 ){
                    won = true;
                }
                else{
                    return;
                }
            }
        }

        if(won) {
            System.out.println("Player+"+player.getID()+" won!");
            //Play a the winner sound "Sound of music!!"
            model.getAudio().playSound(Constants.Sounds.winGame);

            HighScore high = new HighScore();
            high.addNewPlayer(player, player.getScore());

            //Update the view
            EventBus.publish(Events.WIN.toString(),player.getPlayerName());
        }
        else
            return;
    }

    @Override
    public void onTimeOut() {
        if(model.getState().getBattle() != null){
            resolveBattle(false);
            model.getState().setBattle(null);
        }
        else
            System.out.println("TimeOut No battle");

    }
}
